package geecache

import (

	"fmt"
	"log"
	"sync"
)

/*
定义接口Getter和回调函数Get，参数是key，返回值是[]byte
定义函数类型GetterFunc，并实现Getter接口的Get方法
函数类型实现某一个接口，称之为接口型函数，方便使用者在调用的时候既能够传入函数作为参数，也能够传入实现了该接口的
结构体作为参数
*/

type Getter interface {
	Get(key string) ([]byte, error)
}

type GetterFunc func(key string) ([]byte, error)

func (f GetterFunc) Get(key string) ([]byte, error) {
	return f(key)
}

/*
	一个Group可以看成是一个缓存的命名空间，每个Group拥有一个唯一的名称name
	比如可以创建3个Group，缓存学生的成绩命名为scores，缓存学生信息的命名为info，
	缓存学生课程的命名为courses
	第二个属性是getter Getter，即缓存未命中时获取源数据的回调
	第三个属性是mainCache cache，即一开始的并发缓存
	NewGroup用来实例化Group，并将group存储在全局变量groups中
	GetGroup用来特定名称的Group，这里使用了只读锁RLock()，因为不涉及任何冲突变量的写操作
*/
type Group struct {
	name 		string
	getter 		Getter
	mainCache	cache
}
var (
	mu 		sync.RWMutex
	groups = make(map[string]*Group)
)

func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
	if getter == nil {
		panic("nil Getter")
	}
	mu.Lock()
	defer mu.Unlock()
	g := &Group{
		name: name,
		getter: getter,
		mainCache: cache{cacheBytes: cacheBytes},
	}
	groups[name] = g
	return g
}

func GetGroup(name string) *Group {
	mu.Lock()
	g := groups[name]
	mu.Unlock()
	return g
}

/*
	Get 方法实现了上述所说的流程 ⑴ 和 ⑶。
	流程 ⑴ ：从 mainCache 中查找缓存，如果存在则返回缓存值。
	流程 ⑶ ：缓存不存在，则调用 load 方法，load 调用 getLocally
	（分布式场景下会调用 getFromPeer 从其他节点获取），
	getLocally 调用用户回调函数 g.getter.Get() 获取源数据，
	并且将源数据添加到缓存 mainCache 中（通过 populateCache 方法）
*/
func (g *Group) Get(key string) (ByteView, error)  {
	if key == "" {
		return ByteView{}, fmt.Errorf("key is required")
	}

	if v, ok := g.mainCache.get(key); ok {
		log.Println("[GeeCache] hit")
		return v, nil
	}

	return g.load(key)
}

func (g *Group) load(key string) (value ByteView, err error)  {
	return g.getLocally(key)
}

func (g *Group) getLocally(key string) (ByteView, error) {
	bytes, err := g.getter.Get(key)
	if err != nil {
		return ByteView{}, err

	}
	value := ByteView{b: cloneBytes(bytes)}
	g.populateCache(key, value)
	return value, nil
}

func (g *Group) populateCache(key string, value ByteView) {
	g.mainCache.add(key, value)
}

